package com.prolixtech.jaminid;

import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import com.prolixtech.utils.SingletonLogger;

/**
 * The request class encapsulates an HTTP request. It is responsible for
 * splitting up the query, processing POST forms, splitting to URI and URL, and
 * once it is created, it is essentially a data struct for all this information.
 * 
 * @author Constantinos Michael
 * 
 * 
 * 
 * TODO
 */
public class Request {

    private StringBuffer requestMessageBuffer = new StringBuffer();

    private StringBuffer requestBody = new StringBuffer();

    private String paramString;

    private String HTTPMethod;

    private String requestURI;
    
    private Connection conn;

    private Daemon masterDaemon;

    private Protocol protocol;

    private String requestLocation;

    private boolean isbad = false;

    private static int STATUS_WAITING_FOR_METHOD = 0;

    private static int STATUS_WAITING_FOR_PARAMS = 1;

    private static int STATUS_WAITING_FOR_BODY = 2;

    private static int STATUS_COMPLETE = 3;

    private static int STATUS_MALFORMED = -1;

    private int currentStatus = 0;

    private Map<String, String> activeHeader = new HashMap<String, String>();

    private Map<String, String> paramMap = new HashMap<String, String>();

    /**
     * @return if the request was malformed
     */
    public boolean isRequestBad() {
        return isbad;
    }

    private int getStatus() {
        return currentStatus;
    }

    /**
     * Returns the Uniform Resource Identifier
     * 
     * @return the URI Uniform Resource Identifier
     */
    public String getURI() {
        return this.requestURI;
    }

    /**
     * Returns the entire request message
     * 
     * @return the entire request message
     */
    public String getMessage() {
        return this.requestMessageBuffer.toString();
    }

    /**
     * Used in POST to return the body of the request
     * 
     * @return the body of the request (useful in POST)
     */
    public String getBody() {
        return this.requestBody.toString();
    }

    /**
     * Returns the master daemon
     * 
     * @return the master daemon where this request was spawned
     */
    public Daemon getDaemon() {
        return this.masterDaemon;
    }

    /**
     * Returns header
     * 
     * @return the header of this request
     */
    public Map getHeader() {
        return this.activeHeader;
    }

    /**
     * 
     * @return a param map of the request (like Perl)
     */
    public Map<String,String> getParamMap() {
        return this.paramMap;
    }

    /**
     * Return the location sought
     * 
     * @return the location sought (URL)
     */
    public String getLocation() {
        return this.requestLocation;
    }

    /**
     * 
     * @param methodLine the method line of the request
     */
    public void setMethodLine(String methodLine) {
        try {
            String firstLine = methodLine.trim();
            this.HTTPMethod = firstLine.substring(0, firstLine.indexOf(" "));
            this.requestURI = (firstLine.substring(firstLine.indexOf(" "),
                    firstLine.lastIndexOf("HTTP/"))).trim();

            int splitLocation = this.requestURI.indexOf("?");
            if (splitLocation > 0) {
                this.paramString = requestURI.substring(splitLocation + 1);
                // (paramString);
                this.requestLocation = requestURI.substring(0, splitLocation);
                // (this.requestLocation);
                this.processParamString(paramString);

            } else {
                this.requestLocation = this.requestURI;
            }

        } catch (Exception e) {
            this.isbad = true;
            this.currentStatus = Request.STATUS_MALFORMED;
            return;
        }
    }

    /**
     * appends more stuff to body of request
     * @param body what to append
     */
    public void appendToBody(String body) {
        this.requestBody.append(body);
    }

    /**
     * Method Adds Request Lines
     * 
     * @param requestMessage
     *            the message to add
     */
    public void addRequestLines(String requestMessage) {
        if (this.currentStatus == Request.STATUS_COMPLETE) {
            return;
        }

        if (this.currentStatus == Request.STATUS_MALFORMED)
            return;

        this.requestMessageBuffer.append(requestMessage);

        int bodyIndex = requestMessage.indexOf("\r\n\r\n");
        String reqBody = "";

        /*
         * if(bodyIndex>0){ reqBody = requestMessage.substring(bodyIndex+4);
         * requestMessage = requestMessage.substring(0, bodyIndex); }
         */

        StringTokenizer tokenizer = new java.util.StringTokenizer(
                requestMessage, "\n\r");

        if (this.currentStatus == Request.STATUS_WAITING_FOR_METHOD) {
            currentStatus = Request.STATUS_WAITING_FOR_PARAMS;
            setMethodLine(tokenizer.nextToken());
            // process first line
        }

        if (this.currentStatus == Request.STATUS_WAITING_FOR_PARAMS) {

            while (tokenizer.hasMoreTokens()) {
                String token = tokenizer.nextToken();

                int splitLocation = token.indexOf(":");
                if (splitLocation > 0) {

                    String key = (token.substring(0, splitLocation)).trim();
                    String value = (token.substring(splitLocation + 1)).trim();
                    masterDaemon.printlog("KEY [" + key + "] VAL [" + value
                            + "]");

                    this.activeHeader.put(key, value);
                } else {
                    // "Not a keyval pair: " + token;
                }
            }

        }

        if (this.currentStatus == Request.STATUS_WAITING_FOR_BODY) {
            reqBody = requestMessage;
        }
        if (bodyIndex > 0) {
            this.currentStatus = Request.STATUS_WAITING_FOR_BODY;
        }
        if (this.currentStatus == Request.STATUS_WAITING_FOR_BODY) {
            this.appendToBody(reqBody);
        }

        masterDaemon.printlog("[**] METHOD: " + this.HTTPMethod);
        masterDaemon.printlog("[**] URI: " + this.requestURI);
        int lastDotLoc = requestURI.lastIndexOf(".");
        if (lastDotLoc != -1) {
            masterDaemon.printlog("[**] TYPE: "
                    + Protocol.getMIME(requestURI.substring(lastDotLoc)));
        }
        masterDaemon.printlog("[**] BODY: " + requestBody);
    }
    
    /**
     * 
     * @return the ip address of the request
     */
    public String getIPAddressString(){
        return conn.getIPAddressString();
    }

    /**
     * Creates a new request object
     * 
     * @param activeDaemon
     *            the daemon that spawned the request
     * @param connx the connection that produced this request
     */
    public Request(Daemon activeDaemon, Connection connx) {
        this.conn = connx;
        this.masterDaemon = activeDaemon;
        protocol = this.masterDaemon.getProtocol();
    }

    /**
     * processes a parameter string for HTTP query ? parameters
     * 
     * @param paramStringL
     */
    private void processParamString(String paramStringL) {
        try {
            String cookie = (String) this.getHeader().get("Cookie");
            if (cookie != null) {
                String cookieparts[] = cookie.split(";");
                for (int i = 0; i < cookieparts.length; i++) {

                    cookieparts[i] = cookieparts[i].trim();

                    if (cookieparts[i].indexOf("=") > 0) {

                        String valuepair[] = cookieparts[i].split("=");
                        if (valuepair.length == 2) {
                            this.paramMap.put(valuepair[0].trim(), valuepair[1]
                                    .trim());
                        }
                    }
                }

            }
        } catch (Exception e) {
            SingletonLogger.Instance().http(this, "Error processing cookie" + e);
            e.printStackTrace();
        }

        String[] comps = paramStringL.split("&");
        for (int i = 0; i < comps.length; i++) {
            int equalsLoc = comps[i].indexOf("=");
            if (equalsLoc > 0) {
                this.paramMap.put(comps[i].substring(0, equalsLoc),
                        unEscape(comps[i].substring(equalsLoc + 1)));
            } else {
                this.paramMap.put(comps[i], "");
            }
        }
    }

    /**
     * removes the HTTP escape sequence for the query (e.g. %20 and $)
     * 
     * @param s the string to unescape
     * @return the unescaped string
     */
    public String unEscape(String s) {
        if (s == null || s.equals(""))
            return "";
        int lastIndex = -1;

        do {
            lastIndex = s.indexOf('%', lastIndex + 1);

            if (lastIndex >= 0) {
                try {
                    String hex = s.substring(lastIndex + 1, lastIndex + 3);
                    char hin = (char) hex2int(hex);
                    //System.out.println(lastIndex + " Hex is " + hex + " or "
                    //        + hin);
                    String sBefore = s.substring(0, lastIndex);
                    String sAfter = s.substring(lastIndex + 3);

                    s = sBefore + hin + sAfter;

                } catch (Exception e) {
                    SingletonLogger.Instance().exception("Escaping Error", e);
                }
            }
        } while (lastIndex >= 0);

        s = s.replaceAll("\\+", " ");
        return s;
    }

    /**
     * convenience function for converting hex to int
     * 
     * @param x the String to convert
     * @return the converted string
     */
    private static int hex2int(String x) {
        char cha = x.charAt(0);
        char chb = x.charAt(1);

        return hex2int(chb) + hex2int(cha) * 16;

    }

    /**
     * convenience function for converting hex to int
     * 
     * @param a the character to convert
     * @return the converted character
     */
    private static int hex2int(char a) {

        if (Character.isDigit(a)) {
            return Integer.parseInt(String.valueOf(a));
        }
        a = Character.toUpperCase(a);
        if (a >= 'A' && a <= 'F') {
            return a - 'A' + 10;
        }

        throw new IllegalArgumentException("Invalid Hex");
    }

    protected int switchToBody() {
        if (this.HTTPMethod.equals("POST")) {
            try {
                // System.out.println("Switching to body");
                int returnable = 0;// Integer.parseInt(this.paramMap.get("Content-Length").toString());
                this.currentStatus = Request.STATUS_WAITING_FOR_BODY;
                return returnable;
            } catch (Exception e) {
                System.out.println("ABORTED to body");
                e.printStackTrace();
            }
        }
        this.currentStatus = Request.STATUS_COMPLETE;
        return 0;
    }

    protected void switchToCompleted() {
        if (this.HTTPMethod.equals("POST")) {
            this.processParamString(this.requestBody.toString());
        }
    }

    /**
     * 
     * @return the param string
     */
    public String getParamString() {
        return this.paramString;

    }
    
    public String getParam(String key){
        String n = this.paramMap.get(key);
        if(n==null) return "";
        return n;
    }


    public String getParamOrNull(String key){
        String n = this.paramMap.get(key);
        return n;
    }


    public boolean checkParam(String param, String possvalue) {
        if (param == null)
            return false;
        if (getParamMap().get(param) == null)
            return false;
        return (getParamMap().get(param)).equals(possvalue);
    }

    
}